<?php

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

namespace BLL\DAO;
use BLL\DataModel as DM;
/**
 * Description of FormReaderGenerator
 *
 * @author David Wu
 */
class FormReaderGenerator {
    private $dataContract = null;
    private $readerContext = array();
    private $output = array();
    private $objectLevel = 0;

    public function __construct($configName) {
        $this->loadConfig($configName);
    }
    public function loadConfig($configName){
        $path = str_replace(array("\\","/"), DIRECTORY_SEPARATOR, $GLOBALS["AppConfig"]["dataContract"]);
        if (!strrchr($path, DIRECTORY_SEPARATOR)) {
            $path = $path . DIRECTORY_SEPARATOR;
        }
        $filename=  app_path().DIRECTORY_SEPARATOR.$path.$configName.".json";
        if (file_exists($filename)) {
            $this->configFile = $configName;
            $str = file_get_contents($filename);
            $this->dataContract = json_decode($str,true);
        } else {
            throw new \Exception("Failed to find load data contract config file:" . $configName);
        }
        $objectLevel = 0;
        $output = array();
        $readerContext = array();
    }    
    public function generate(){

        $this->templateReplace( "fileHead", array("{generateDate}", "{ClassName}"), array(date('M-d-Y'), $this->dataContract["outPutName"]));
        if(isset($this->dataContract["additionalFunctionFile"])){
            foreach($this->dataContract["additionalFunctionFile"] as $func){
                $this->templateReplace( "additionalFunctionFile", 
                        array("{fileName}"), 
                        array($func));
            }
        }
        foreach ($this->dataContract["target"] as $target) {
            $this->aNode($target);
        }
        $this->templateReplace( "fileTail", array(), array());
        $str = \BLL\Common\CodeGeneratorHelper::generatr($this->output);
        $path = app_path() . DIRECTORY_SEPARATOR . "BLL" . DIRECTORY_SEPARATOR . "DataContract" . DIRECTORY_SEPARATOR;
        file_put_contents($path . $this->dataContract["outPutName"].".php", $str);

        return $str;
        
    }
    
    private function  aNode( $json, $model = null){
        
        $this->objectLevel++;
        if(!$model){
            if ($json["type"] === "Model") {
                $model = DM\DataNodeDescription::getNodeByFullName($json["class"]);
            } else {
                $model = DM\DataNodeDescription::getNodeByFullName($json["configName"]);
            }
        }
        if(isset($json["mode"]) && $json["mode"] === "clearBeforeCreate"){
            $this->templateReplace( "clearNode", array("{object}","{nodeName}"), array("obj".($this->objectLevel-1), $model->name));
        }
        foreach ($json["source"] as $source){
            $withIndex = null;
            if(isset($source["withIndex"])){
                $withIndex = $source["withIndex"];
                $min = intval($source["min"]);
                $max = intval($source["max"]);
            }else{
                $min = 1;
                $max = 1;
            }
            $sourceName = isset($source["sourceName"])?"Source = ".$source["sourceName"]." ; ":"";
                    
            for($i = $min; $i<=$max; $i++ ){
                if($withIndex){
                    $ctx = array();
                    $ctx["node"]= $model->name;
                    $ctx["withIndex"] = $withIndex;
                    $ctx["index"] = $i;
                    array_push($this->readerContext,$ctx);
                }
                $context = "";
                foreach($this->readerContext as $ctx){
                    $context .= $context===""?"":" ; ";
                    $context .= $ctx["node"]." index ".$ctx["index"];
                }
                $allItems = isset($source["allItems"]) && $source["allItems"]==="True"; 
                $this->templateReplace( "newRowBegin", array("{ctl}","{path}","{context}"), array("ttt".$this->objectLevel, $model->getFullName() ,$sourceName.$context));
                $this->templateReplace( "beginReadFields", array("{path}"), array($model->getFullName()));
                foreach ($model->items as $item) {
                    if (!$allItems && !isset($source["items"][$item->name])) {
                        continue;
                    }
                    $this->aField( isset($source["items"][$item->name])?$source["items"][$item->name]:null, $item);
                }
                $this->templateReplace( "endReadFields", array("{path}"), array($model->getFullName()));
                $this->nodeHead( $json, $model);
                $this->nodeTail( $json, $model);
                if (isset($source["outputName"])) {
                    $this->templateReplace("putOutputObject", 
                            array("{ObjName}", "{objVar}"), 
                            array( $source["outputName"] , 'obj'.$this->objectLevel ));
                }
                if(isset($source["nodes"])){
                    foreach ($source["nodes"] as $nodeName=>$node) {
                        $nodeModel = $model->getSlaveNodeByName($nodeName);
                        $this->aNode( $node, $nodeModel);
                    }
                }
                $this->templateReplace( "newRowEnd", array(), array());
                if($withIndex){
                    array_pop($this->readerContext);
                }
            }
        }
        $this->objectLevel--;
    }
    
    private function nodeHead( $json, $model){
        
         if($json["mode"]==="keyFromInput"){
            $this->templateReplace( "existModelFromInput",
                array("{key}","{varName}", "{className}", "{idField}"), 
                array($json["key"], 'obj'.$this->objectLevel, $json["class"], $model->idField));
        }else
        if($json["mode"]==="keyFromSession"){
            $this->templateReplace( "existModelFromSession",
                array("{key}","{varName}", "{className}", "{idField}"), 
                array($json["key"], 'obj'.$this->objectLevel, $json["class"], $model->idField));
        }else{
            $clearMode = isset($json["mode"]) && $json["mode"] === "clearBeforeCreate" ;
            $selAttrs = $this->getSelAttr($model);
            if(!$clearMode && count($selAttrs) > 0) {
                $this->templateReplace( "setSelPrepare", array("{varName}"), array("selAttr_".$model->name));
                foreach ($selAttrs as $key) {
                    if ($key === $model->masterIDField) {
                        $this->templateReplace( "setSel", 
                            array("{varName}","{key}","{value}"), 
                            array("selAttr_".$model->name,$key,'$obj'.($this->objectLevel-1)."->".$model->masterNode->idField));
                    } else {
                        $this->templateReplace( "setSel", 
                            array("{varName}","{key}","{value}"), 
                            array("selAttr_".$model->name,$key,'$arrts["'.$key.'"]'));
                    }
                }
                $this->templateReplace( "firstOrNewModel",
                    array("{varName}", "{className}", "{selAttr}", "{idField}"), 
                    array('obj'.$this->objectLevel, $model->getFullName(), "selAttr_".$model->name ,$model->idField));
            }else{
                $this->templateReplace( "newModel",
                    array("{varName}", "{className}", "{idField}"), 
                    array('obj'.$this->objectLevel, $model->getFullName(), $model->idField));
            }
        }
        //objectLevel > 1 means this node is a slave node
        if($this->objectLevel>1){
            $this->templateReplace( "setMasterAndRoot", 
                    array("{varName}", "{masterID}",  "{masterIDValue}", "{rootID}",  "{rootIDValue}"), 
                    array('obj'.$this->objectLevel, 
                        $model->masterIDField, '$obj'.($this->objectLevel-1)."->".$model->masterNode->idField,
                        $model->getRootIDField(), '$obj'.($this->objectLevel-1)."->".$model->masterNode->getRootIDField()
                    ));
        }
    }
    
    private function nodeTail( $json, $model){
        if(isset($json["saveTo"]) && $json["saveTo"]==="db"){
            $this->templateReplace( "saveToDB",
                array("{varName}"), 
                array('obj'.$this->objectLevel));
            
        }
    }

    private function aField( $json, $item){
        if(!$json){
            $json = array("name"=>$item->name);
        }
        $this->templateReplace( "isset", array(), array());
        $this->getASlice($json, "input_".$item->name, $item->dataType);
        $stop = isset($json["stopWhenUnset"])?$json["stopWhenUnset"]:"false";
        if($stop ==="true"){
            $this->templateReplace( "stopWhenUnset",array(),array());
        }
        $stopE = isset($json["stopWhenEquals"])?$json["stopWhenEquals"]:null;
        if($stopE!==null){
            $this->templateReplace( "stopWhenEquals",array("{varName}","{value}"),array("input_".$item->name,$stopE));
        }
        $this->templateReplace( "setItem", array("{varName}", "{itemName}"), array("input_".$item->name, $item->name));
    }

    private function getASlice($json, $varName, $dataType){

        $type = isset($json["type"])?$json["type"]:"input";

        if($type === "const"){
            $this->templateReplace( "varFromConst".($dataType==="string"||$dataType==="String"?"WithString":""), 
                    array("{const}", "{dataType}","{varName}"), 
                    array($this->getIndexedName($json["name"]), $dataType , $varName));   
        }

        if($type === "input"){
            if(!isset($json["special"]))
            {       
                $this->templateReplace( "varFromInput".($dataType==="string"||$dataType==="String"?"WithString":""), 
                    array("{inputName}", "{dataType}","{varName}"), 
                    array($this->getIndexedName($json["name"]), $dataType, $varName));            
            }else{
                $this->templateReplace( "varFromInputWithSpecial".($dataType==="string"||$dataType==="String"?"WithString":""), 
                    array("{inputName}", "{dataType}","{varName}","{specialFunction}"), 
                    array($this->getIndexedName($json["name"]), $dataType, $varName , $json["special"]["name"]));            
            }
        }
        if($type === "server"){
                $this->templateReplace( "varFromServerVars", 
                    array("{varName}","{serverVars}"), 
                    array($varName,$json["name"]));            
        }
        if($type === "function"){
            $params = "";
            if(isset($json["paramters"])){
                $i=0;
                foreach($json["paramters"] as $p){
                    $type = isset($p["dataType"])?$p["dataType"]:"String";
                    $this->getASlice($p, 'p'.$i, $type);
                    $params = $i > 0? $params." , ".'$p'.$i : $params.'$p'.$i;
                    $i++;
                }
            }
            $this->templateReplace( "varFromFunction", 
                    array("{varName}","{functionName}", "{params}"), 
                    array($varName, $this->getIndexedName($json["name"]), $params));
        }
    }
    private function getSelAttr($model){
        $firstUG = null;
        $selAttrs = array();
        foreach ($model->items as $item) {
            if (!$item->uniqueGroup) {
                continue;
            }
            if (!$firstUG) {
                $firstUG = $item->uniqueGroup;
                if ($model->masterNode) {
                    array_push($selAttrs, $model->masterIDField);
                }
            }
            if ($firstUG != $item->uniqueGroup) {
                continue;
            }
            array_push($selAttrs, $item->fieldName);
        }
        return $selAttrs;
    }
    
    
    private function getIndexedName($name){
        foreach($this->readerContext as $ctx){
            if($ctx["withIndex"]){
                $name = str_replace("{".$ctx["withIndex"]."}", $ctx["index"], $name);
            }
        }
        return $name;
    }
    private function templateReplace( $temp, $search,$replace){
        foreach($this->template[$temp] as $s){
            $this->output[] = str_replace($search, $replace, $s);
        }
    }
    
    private $template = array(
        "fileHead" => array(
            '<?php',
            '//This file is auto generated by APP, plaease do not modify it.',
            '//Generated in {generateDate}.',
            'namespace BLL\DataContract;',
            '',
            'class {ClassName}{',
            'public static function read(){',
            '$OutputObjects = array();'
        ),
        "fileTail" => array(
            'return $OutputObjects;',
            '}',
            '}',
            '//File ends hear.'
        ),
        "putOutputObject" => array(
            'if ( !isset( $OutputObjects["{ObjName}"] )){',
            '$OutputObjects["{ObjName}"]=array();',
            '}',
            '$OutputObjects["{ObjName}"][] = ${objVar};',
        ),
        "additionalFunctionFile" => array(
            'include app_path().DIRECTORY_SEPARATOR."{fileName}.php";'
        ),
        "existModelFromInput" => array(
            'if(!Input::has("{key}"){',
                'throw new \Exception("require key from request :{key}")',
            '}',
            '$key = Input::get("{key}");',
            '${varName} = {className}::find($key);',
            'if(!${varName}){',
                'throw new \Exception("Object not found with id ")',
            '}',
            '${varName}->fill($attrs);'
            ),
        "existModelFromsSession" => array(
            'if(!isset($_SESSION["{key}"){',
                'throw new \Exception("require key from session :{key}")',
            '}',
            '$key = $_SESSION["{key}"];',
            '${varName} = {className}::find($key);',
            'if(!${varName}){',
                'throw new \Exception("Object not found with id ")',
            '}',
            '${varName}->fill($attrs);'
            ),
        
        "newModel" => array(
            '${varName} = new {className}($attrs);',
            '${varName}->{idField} = uniqid();',
            ),
        "newObject" => array(
            '${varName} = new \\stdClass();'
            ),
        "newRowBegin" => array(
            '//read node {path}',
            '//{context}',
            'for(${ctl}=1;${ctl}<2;${ctl}++){',
            '$attrs = array();'
        ),
        "newRowEnd" => array(
            '}'
        ),
        "beginReadFields" => array(
            '//begin to read fields of {path}',
            'if(true){'
        ),
        "endReadFields" => array(
            '}'
        ),
        "varFromInput" => array(
            'if(Input::has("{inputName}")){',
                '${varName} = Input::get("{inputName}");',
                '${varName} = \BLL\DataModel\DataType::getValueFromString("{dataType}", ${varName});',
            '}else{',
                '$isset=false;',
            '}'
        ),
        "isset" => array(
            '$isset=true;'
        ),
        "varFromServerVars" => array(
            '${varName} = $_Server("{serverVars}");'
        ),
        "varFromInputWithString" => array(
            'if(Input::has("{inputName}")){',
                '${varName} = Input::get("{inputName}");',
            '}else{',
                '$isset=false;',
            '}'
        ),
        "varFromInputWithSpecial" => array(
            'if(Input::has("{inputName}")){',
                '${varName} = {specialFunction}(Input::get("{inputName}"));',
                '${varName} = \BLL\DataModel\DataType::getValueFromString("{dataType}", ${varName});',
            '}else{',
                '$isset=false;',
            '}'
        ),
        "varFromInputWithSpecialWithString" => array(
            'if(Input::has("{inputName}")){',
                '${varName} = {specialFunction}(Input::get("{inputName}"));',
            '}else{',
                '$isset=false;',
            '}'
        ),
        "varFromConst" => array(
            '${varName} = \BLL\DataModel\DataType::getValueFromString("{dataType}", {const});'
        ),
        "varFromConstWithString" => array(
            '${varName} = {const};'
        ),
        "setItem" => array(
            '$arrts["{itemName}"] = ${varName};'
        ),
        "varFromFunction" => array(
            'if($isset){',
                '${varName} ={functionName}({params});',
            '}'
        ),
        "stopWhenUnset"=>array(
            'if(!$isset){',
                'break;',
            '}'
        ),
        "stopWhenEquals" => array(
            'if(!isset(${varName})|| ${varName} === {value} ){',
                'break;',
            '}'
        ),
        "clearNode" => array(
            '${object}->{nodeName}()->delete();'
        ),
        "setSelPrepare" => array(
            '${varName} = array();'
        ),
        "setSel" => array(
            '${varName}["{key}"] = {value};'
        ),
        "firstOrNewModel" => array(
            '${varName} = {className}::firstOrNew(${selAttr});',
            'if(!isset(${varName}->{idField}))${varName}->{idField} = uniqid();',
        ),
        "setMasterAndRoot" => array(
            '${varName}->{masterID} = {masterIDValue};',
            '${varName}->{rootID} = {rootIDValue};',
        ),
        "saveToDB" => array(
            '${varName}->save();'
        )
    );
}
